#include "./BSP/I2CVirtual/I2CVirtual.h"
#include "./SYSTEM/delay/delay.h" //提供延时函数支持
#include "./SYSTEM/usart/usart.h"

#ifdef I2C_VIRTUAL

static uint16_t I2C_Virtual_SDA_Pin;
static GPIO_TypeDef *I2C_Virtual_SDA_Port;
volatile unsigned long *I2C_Virtual_SDA_OUT_Addr;//static
volatile unsigned long *I2C_Virtual_SDA_IN_Addr;//static
volatile unsigned long *I2C_Virtual_SCL_OUT_Addr;//static
 
 uint8_t I2C_Virtual_ack;//从机应答信号 应答值为1


/**
 * @brief 配置模拟I2C的引脚端口。
 * 		主要实现了MCU的GPIO端口配置，SDA需要配置为开漏输出，SCL需要配置为推挽输出。
 * 		此函数实现与硬件平台相关。
 * @param SDA_Port: 'A'~'G'
 * @param SDA_Pin: 0~15
 * @param SCL_Port: 'A'~'G'
 * @param SCL_Pin: 0~15
 */
void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
{
	uint16_t I2C_Virtual_SDA_Pin;
	uint16_t I2C_Virtual_SCL_Pin;
	GPIO_TypeDef *I2C_Virtual_SDA_Port;
	GPIO_TypeDef *I2C_Virtual_SCL_Port;
	GPIO_InitTypeDef GPIO_IniT;

	switch (SDA_Port)
	{
	case 'A':
		I2C_Virtual_SDA_Port = GPIOA;
        __HAL_RCC_GPIOA_CLK_ENABLE();
		break;
	case 'B':
		I2C_Virtual_SDA_Port = GPIOB;
		__HAL_RCC_GPIOB_CLK_ENABLE();
		break;
	case 'C':
		I2C_Virtual_SDA_Port = GPIOC;
		__HAL_RCC_GPIOC_CLK_ENABLE();
		break;
	case 'D':
		I2C_Virtual_SDA_Port = GPIOD;
		__HAL_RCC_GPIOD_CLK_ENABLE();
		break;
	case 'E':
		I2C_Virtual_SDA_Port = GPIOE;
		__HAL_RCC_GPIOE_CLK_ENABLE();
		break;
	case 'F':
		I2C_Virtual_SDA_Port = GPIOF;
		__HAL_RCC_GPIOF_CLK_ENABLE();
		break;
	case 'G':
		I2C_Virtual_SDA_Port = GPIOG;
		__HAL_RCC_GPIOG_CLK_ENABLE();
		break;
	default:
		break;
	}

	I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

	switch (SCL_Port)
	{
	case 'A':
		I2C_Virtual_SCL_Port = GPIOA;
        __HAL_RCC_GPIOA_CLK_ENABLE();
		break;
	case 'B':
		I2C_Virtual_SCL_Port = GPIOB;
		__HAL_RCC_GPIOB_CLK_ENABLE();
		break;
	case 'C':
		I2C_Virtual_SCL_Port = GPIOC;
		__HAL_RCC_GPIOC_CLK_ENABLE();
		break;
	case 'D':
		I2C_Virtual_SCL_Port = GPIOD;
		__HAL_RCC_GPIOD_CLK_ENABLE();
		break;
	case 'E':
		I2C_Virtual_SCL_Port = GPIOE;
		__HAL_RCC_GPIOE_CLK_ENABLE();
		break;
	case 'F':
		I2C_Virtual_SCL_Port = GPIOF;
		__HAL_RCC_GPIOF_CLK_ENABLE();
		break;
	case 'G':
		I2C_Virtual_SCL_Port = GPIOG;
		__HAL_RCC_GPIOG_CLK_ENABLE();
		break;
	default:
		break;
	}

	I2C_Virtual_SCL_Pin = (uint16_t) (0x0001 << SCL_Pin);

	GPIO_IniT.Pin = I2C_Virtual_SDA_Pin;
	GPIO_IniT.Mode = GPIO_MODE_OUTPUT_OD;
	GPIO_IniT.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_IniT);

	GPIO_IniT.Pin = I2C_Virtual_SCL_Pin;
	GPIO_IniT.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_IniT.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(I2C_Virtual_SCL_Port, &GPIO_IniT);

}



/**配置引脚之后需要使用此函数，以使能引脚输入输出
 * @brief 使本套库函数可以支持多个独立的软件模拟I2C总线。
 * 		通过切换I2C总线引脚，以分别控制不同的芯片。
 * 		一般在配置模拟I2C的引脚端口之后，需要调用此函数设置总线。
 * @param SDA_Port: 'A'~'G'
 * @param SDA_Pin: 0~15
 * @param SCL_Port: 'A'~'G'
 * @param SCL_Pin: 0~15
 */
void I2C_Virtual_SwitchBus(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
{

	switch (SDA_Port)
	{
	case 'A':
		I2C_Virtual_SDA_Port = GPIOA;
	
		I2C_Virtual_SDA_OUT_Addr = PAout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PAin_Addr(SDA_Pin);
		break;
	case 'B':
		I2C_Virtual_SDA_Port = GPIOB;
		I2C_Virtual_SDA_OUT_Addr = PBout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PBin_Addr(SDA_Pin);
		break;
	case 'C':
		I2C_Virtual_SDA_Port = GPIOC;
		I2C_Virtual_SDA_OUT_Addr = PCout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PCin_Addr(SDA_Pin);
		break;
	case 'D':
		I2C_Virtual_SDA_Port = GPIOD;
		I2C_Virtual_SDA_OUT_Addr = PDout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PDin_Addr(SDA_Pin);
		break;
	case 'E':
		I2C_Virtual_SDA_Port = GPIOE;
		I2C_Virtual_SDA_OUT_Addr = PEout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PEin_Addr(SDA_Pin);
		break;
	case 'F':
		I2C_Virtual_SDA_Port = GPIOF;
		I2C_Virtual_SDA_OUT_Addr = PFout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PFin_Addr(SDA_Pin);
		break;
	case 'G':
		I2C_Virtual_SDA_Port = GPIOG;
		I2C_Virtual_SDA_OUT_Addr = PGout_Addr(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = PGin_Addr(SDA_Pin);
		break;
	default:
		break;
	}

	I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

	switch (SCL_Port)
	{
	case 'A':
		I2C_Virtual_SCL_OUT_Addr = PAout_Addr(SCL_Pin);
		break;
	case 'B':
		I2C_Virtual_SCL_OUT_Addr = PBout_Addr(SCL_Pin);
		break;
	case 'C':
		I2C_Virtual_SCL_OUT_Addr = PCout_Addr(SCL_Pin);
		break;
	case 'D':
		I2C_Virtual_SCL_OUT_Addr = PDout_Addr(SCL_Pin);
		break;
	case 'E':
		I2C_Virtual_SCL_OUT_Addr = PEout_Addr(SCL_Pin);
		break;
	case 'F':
		I2C_Virtual_SCL_OUT_Addr = PFout_Addr(SCL_Pin);
		break;
	case 'G':
		I2C_Virtual_SCL_OUT_Addr = PGout_Addr(SCL_Pin);
		break;
	default:
		break;
	}
}


/**
 * @brief Switch data bus output state.
 */
void I2C_Virtual_SetSDA_Out(void)
{
	GPIO_InitTypeDef GPIO_IniT;

	GPIO_IniT.Pin = I2C_Virtual_SDA_Pin;
	GPIO_IniT.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_IniT.Mode= GPIO_MODE_OUTPUT_OD;

	HAL_GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_IniT);
}



/**
 * @brief Switch data bus input state.
 */
void I2C_Virtual_SetSDA_In(void)
{
	GPIO_InitTypeDef GPIO_IniT;

	GPIO_IniT.Pin = I2C_Virtual_SDA_Pin;
	GPIO_IniT.Mode = GPIO_MODE_INPUT;
	GPIO_IniT.Speed = GPIO_SPEED_FREQ_HIGH;

	HAL_GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_IniT);
}


/**
 * @brief Initialization of the virtual I2C.
 */
void I2C_Virtual_Init(void)
{
	*I2C_Virtual_SCL_OUT_Addr = 1;
	
	I2C_Virtual_SetSDA_Out();
	*I2C_Virtual_SDA_OUT_Addr = 1;
}



/**
 * @brief Start I2C.
 */
void I2C_Virtual_Start()
{
    
	*I2C_Virtual_SDA_OUT_Addr = 1;			//发送起始条件的数据信号
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);										//起始条件建立时间大于4.7us,延时

	*I2C_Virtual_SDA_OUT_Addr = 0;		//发送起始信号
	delay_us(10);										//起始条件锁定时间大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;			//钳住I2C总线，准备发送或接收数据
	delay_us(2);

}



/**
 * @brief Stop I2C.
 */
void I2C_Virtual_Stop()
{
	*I2C_Virtual_SCL_OUT_Addr = 0;
	delay_us(2);

	*I2C_Virtual_SDA_OUT_Addr = 0;	//发送结束条件的数据信号
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;		//发送结束条件的时钟信号
	delay_us(10);									//结束条件建立时间大于4μ

	*I2C_Virtual_SDA_OUT_Addr = 1;		//发送I2C总线结束信号
	delay_us(10);
}



/**
 * @brief 主机字节数据发送函数
 * 		将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,
 * 		并对此状态位进行操作.(不应答或非应答都使ack=0 假)
 * 		发送数据正常，ack=1; ack=0表示被控器无应答或损坏。
 * @retval 应答位
 * 		@arg 0:有应答
 * 		@arg 1:无应答
 */
uint8_t I2C_Virtual_SendByte(uint8_t c)
{

	for (uint8_t BitCnt = 0; BitCnt < 8; BitCnt++)	//要传送的数据长度为8位
	{
		if ((c << BitCnt) & 0x80)	//判断发送位
			*I2C_Virtual_SDA_OUT_Addr = 1;
		else
			*I2C_Virtual_SDA_OUT_Addr = 0;

		delay_us(1);

		*I2C_Virtual_SCL_OUT_Addr = 1;	//置时钟线为高，通知被控器开始接收数据位
		delay_us(10);	//保证时钟高电平周期大于4μ

		*I2C_Virtual_SCL_OUT_Addr = 0;
	}

	I2C_Virtual_WaitAck(1000);
	
	return I2C_Virtual_ack;
}



/**
 * @brief 主机字节数据接收函数
 * 		用来接收从器件传来的数据,并判断总线错误(不发应答信号)，
 * 		发完后请用应答函数。
 */
uint8_t I2C_Virtual_RcvByte()
{
	uint8_t retc;
	uint8_t BitCnt;

	retc = 0;

	*I2C_Virtual_SDA_OUT_Addr = 1;	//置数据线为输入方式

	for (BitCnt = 0; BitCnt < 8; BitCnt++)
	{
		delay_us(1);
		*I2C_Virtual_SCL_OUT_Addr = 0;	//置时钟线为低，准备接收数据位
		delay_us(10);	//时钟低电平周期大于4.7us
		*I2C_Virtual_SCL_OUT_Addr = 1;	//置时钟线为高使数据线上数据有效
		//delay1us();
		retc = retc << 1;
		I2C_Virtual_SetSDA_In();

		if (*I2C_Virtual_SDA_IN_Addr == 1)
			retc = retc + 1;	//读数据位,接收的数据位放入retc中

		I2C_Virtual_SetSDA_Out();
		delay_us(1);
	}

	*I2C_Virtual_SCL_OUT_Addr = 0;
	delay_us(2);

	return (retc);
}



/**
 * @brief 主机应答函数
 */
void I2C_Virtual_Ack(void)
{
	*I2C_Virtual_SDA_OUT_Addr = 0;
	delay_us(2);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);	//时钟低电平周期大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;  	//清时钟线，钳住I2C总线以便继续接收
	delay_us(2);
}



/**
 * @brief 主机不应答函数
 */
void I2C_Virtual_NoAck(void)
{
	*I2C_Virtual_SDA_OUT_Addr = 1;
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);	//时钟低电平周期大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;	    //清时钟线，钳住I2C总线以便继续接收
	delay_us(2);
}


/**
 * @brief 主机等待应答函数
 * @param time: wait for timeout
 */
void I2C_Virtual_WaitAck(uint16_t time)
{
	uint16_t timeout = 0;

	*I2C_Virtual_SDA_OUT_Addr = 1;	//8位发送完后释放数据线，准备接收应答位
	I2C_Virtual_SetSDA_In();

	*I2C_Virtual_SCL_OUT_Addr = 1;

	delay_us(1);

	while(*I2C_Virtual_SDA_IN_Addr != 0)
	{
		if(timeout++>time)
		{
			I2C_Virtual_ack = 0;
			
			I2C_Virtual_SetSDA_Out();
			I2C_Virtual_Stop();

			return;
		}
	}

	I2C_Virtual_ack = 1;

	I2C_Virtual_SetSDA_Out();
	*I2C_Virtual_SCL_OUT_Addr = 0;

	delay_us(2);
}

#endif


